/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Threading;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C9740011 : ValidationTest
    {

        private static int Count = 1234;


        private static bool AbortablePartCompleted = false;
        private static bool RendezvousEntered = false;
        private const double Delay_Time = Impdef.SwitchToNewTask;


        private static KeyPO keyPO = null;


        internal class TransactioCancelled : Exception { }


        internal enum KeyEnum
        {
            None,
            Cancel,
            Deposit,
            Withdraw
        }


        internal class ATMCardType
        {
            public int Number { get; set; }
            public int Pin { get; set; }
        }


        internal class KeyPO : Protected
        {
            private KeyEnum current = KeyEnum.None;


            [ReadWriteOperation]
            internal Action<KeyEnum> Set { get; private set; }

            [ReadOnlyOperation]
            internal Func<KeyEnum> Value { get; private set; }


            public KeyPO()
            {
                ReadWriteOperation(Set, (KeyEnum value) =>
                {
                    current = value;
                });


                ReadOnlyOperation(Value, () =>
                {
                    return current;
                });
            }

        }


        internal class ATMKeybordTask : ValidationTestTask
        {

            [Entry]
            internal Action CancelPressed { get; private set; }

            private KeyEnum lastKeyPressed = KeyEnum.None;


            internal ATMKeybordTask(IValidationTestResult result)
                : base( result)
            {
            }


            protected override void Body()
            {
                bool exit = false;

                while (!exit)
                {
                    // Force entry calls to be queued
                    lastKeyPressed = ListenForInput();

                    Select(s => s.
                        When(() => lastKeyPressed == KeyEnum.Cancel).
                            Accept(CancelPressed, () =>
                            {
                                RendezvousEntered = true;
                            }).
                            Then(() =>
                            {
                                // User has cancelled the transaction so we exit the
                                // loop and allow the task to terminate
                                exit = true;
                            }).
                        Else(() =>
                        {
                            Ada.Delay(Impdef.SwitchToNewTask);
                        }));
                }
            }
            protected override void Exception(Exception ex)
            {
                // Unexpected Exception in ATMKeyboardTask
                Result.TestFailed();
            }

        }


        private static void ReadCard(ATMCardType card)
        {
            card.Number = 9999;
            card.Pin = 111;
        }


        private static KeyEnum ListenForInput()
        {
            
            // Model the situation where the user does not press cancel thus
            // allowing validation to complete

            Ada.Delay(Delay_Time);  
            // Long enough to force queuing on Keyboard.Cancel_Pressed.
    
            return keyPO.Value();
        }


        private static void ValidateCard(ATMCardType card, IValidationTestResult result)
        {
            Count = (Count + 1) % card.Pin;

            // Simulate a validation activity which is longer than the time
            // taken in Listen_For_Input but not inordinately so.
            Ada.Delay(Delay_Time * 2);

        }


        private static void PerformTransaction(ATMCardType card, IValidationTestResult result)
        {
            if (RendezvousEntered)
                // Triggering statement completed
                result.TestFailed();

            if (Count == 1234)
                // Abortable part did not execute
                result.TestFailed();

            if (!AbortablePartCompleted)
                // Abortable part did not complete
                result.TestFailed();
        }


        [Test(Description =
            "Check that the sequence of statements of the triggering alternative" +
            "of an asynchronous select statement is not executed if the triggering" +
            "statement is a task entry call, and the entry is not accepted" +
            "before the abortable part completes. " +
            "Check that the call queued on the entry is cancelled.")]
        public void Test()
        {
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();
            
            ATMKeybordTask keyboard = null;
            ATMCardType card = new ATMCardType();

            ReadCard(card);

            Ada.Declare(() =>
            {
                keyPO = new KeyPO();
                keyboard = new ATMKeybordTask(result);

                // Setup test expectations

                Expect.Call(result.TestFailed).Repeat.Never();

                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                //
                // Asynchronous select is tested here
                // 
                
                Ada.Select(s =>
                    // Entry call is initially queued, so abortable part starts.
                    s.Call(keyboard.CancelPressed)().
                    Then(() =>
                    {
                        // This would be executed if we completed the rendezvous
                        throw new TransactioCancelled();
                    }).
                ThenAbort(() =>
                {
                    ValidateCard(card, result);
                    AbortablePartCompleted = true;
                }));

                PerformTransaction(card, result);

                // Now clear the entry barrier to allow the rendezvous to complete
                // if the triggering call has not been cancelled 
                keyPO.Set(KeyEnum.Cancel);

                // Allow it to take place
                Ada.Delay(Delay_Time);

                if (RendezvousEntered)
                    // Triggering Call was not cancelled
                    result.TestFailed();

                // clean up.  (Note: the task will only exit the
                // loop and terminate if the call hanging on the 
                // entry is executed.)  
                keyboard.Abort();
            },
            Exception: (ex) =>
            {
                if (ex is TransactioCancelled)
                {
                    // Triggering alternative sequence of statements executed
                    result.TestFailed();
                }
                else
                {
                    // Wrong exception raised
                    result.TestFailed();
                }
                return false;
            });

        }

    }

}
